﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OverStore.Configuration.Builder;
using System.Data.SqlServerCe;
using OverStore.Configuration.Components;
using OverStore.Runtime.Storage;
using OverStore.Exceptions;
using OverStore.Configuration.Builder.Fluent;

namespace OverStore.TutorialAsTest.Tutorial1.Lesson2_1
{
    /// <summary>
    /// This is continuation of Lessson 2. 
    /// It talks about very important thing related to data readers: relations readers and transactions.
    /// 
    /// We will use MyMoney.sdf database for demonstrating relations between object readers and transactions.
    /// 
    /// Generally: If physical data reader is opened inside transaction, it must be closed before transaction ends.
    ///            In OverStore, every SQL operation is bound to transaction, so every data reader implicity opened on 
    ///            reading set of object is also bound to a transaction.
    ///            
    ///            If physical data reader is opened before session disposing, it must be disposed with session.
    ///            In exceptional cases it must be disposed _after_ session disposing.
    /// 
    /// All these rules are applied only to opened <see cref="IDataReader"/> objects.
    /// Note: it is important to understand that actually data reader is opened on first IPersistenceSession.CreateReader{TEntity}().GetEnumerator().MoveNext() method call.
    /// 
    /// If <see cref="IPersistenceSession.CreateReader{TItem}"/> called on disposed session, the method throws <see cref="ObjectDisposedException"/>.
    /// If <see cref="IPersistenceSession.CreateReader{TItem}"/> method called before session dispose, calling <see cref="IEnumerable{T}.GetEnumerator"/> method 
    /// on resulting enumerable throws <see cref="ObjectDisposedException"/>.
    /// 
    /// 
    /// OverStore may be configured for different reaction on ending transaction while opened readers are exists:
    /// - It may throw exception. This is recommended behavior, because in most cases existing of opened reader outside session scope is error. 
    ///   But sometimes it is required for using lazy fetching, for example when using reader as data source for UI.
    ///
    /// - It may forcibly close readers. 
    /// 
    /// - It may postpone transaction ending till reader will be closed.
    /// </summary>
    [TestClass]
    public class Lesson2_1
    {
        public class PlaceType
        {
            public int Type { get; set; }

            public string Name { get; set; }

            public string Description { get; set; }
        }

        private static IPersistenceSessionFactory CreatePlaceTypeConfig(string connectionString, ProcessOpenedReadersMode onCommit)
        {
            return OverStoreFluent.Configure()
                        .LogToConsole()
                        .ConnectToDatabase<SqlCeConnection>(connectionString, onCommit)
                        .MapClassToTable<PlaceType, int>("PlaceType")
                            .MapKey("Type", e => e.Type, (e, id) => e.Type = id, Component.IdentifierGenerator.Seq<PlaceType>(-1, -1))
                            .MapScalarValue<string>("Name", e => e.Name, (e, v) => e.Name = v)
                            .MapNullableScalarValue<string>("Description", e => e.Description, (e, v) => e.Description = v)
                            .ParameterlessReader(true)
                                .ReadCommand()
                                    .UseSqlCommand("select * from PlaceType")
                                .EndCommand()
                                .WithIdentifierProvider(Component.IdentifierProvider.Delegate<int>(r => r.GetInt32("Type")))
                            .EndReader()
                        .EndTableMapping()
                   .End();
        }

        #region Session Disposing

        [TestMethod]
        [ExpectedException(typeof(ObjectDisposedException))]
        public void TryOpenReaderOnDisposedSession()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            var session = CreatePlaceTypeConfig(connectionString, ProcessOpenedReadersMode.ThrowException).CreateSession();
            session.Dispose();
            var count = session.CreateReader<PlaceType>();
        }

        [TestMethod]
        [ExpectedException(typeof(ObjectDisposedException))]
        public void TryStartReaderOnDisposedSession()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            var session = CreatePlaceTypeConfig(connectionString, ProcessOpenedReadersMode.ThrowException).CreateSession();
            var reader = session.CreateReader<PlaceType>();
            session.Dispose();
            var en = reader.GetEnumerator(); // Must throws ObjectDisposedException
        }

        /// <summary>
        /// This test checks situation may cause a database lock.
        /// Steps to reproduce:
        /// 
        /// - Create session 1.
        /// - Open reader using session 1 outside operation scope.
        /// - Dispose session 1
        /// - Create session 2
        /// - Try to get single instance of type read from session 1 using session 2
        /// </summary>
        [TestMethod]
        public void DisposeAllToAvoidLock()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();

            using (var session = CreatePlaceTypeConfig(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders).CreateSession())
            {
                var reader = session.CreateReader<PlaceType>().Cache();
                var en = reader.GetEnumerator();
                en.MoveNext();
            }
            using (var session = CreatePlaceTypeConfig(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CreateSession())
            {
                var placeType = session.GetByIdentifier<PlaceType, int>(2);
            }
        }

        #endregion
    }
}
